package com.hanks.library;
/*
 * Created by Hanks
 * Copyright (c) 2015 hanks. All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import java.util.Optional;

public class AnimateCheckBox extends Component implements Component.LayoutRefreshedListener, Component.DrawTask{

    private static final int DEFAULT_LINE_WIDTH = 4;
    private static final Color DEFAULT_LINE_COLOR = Color.WHITE;
    private static final Color DEFAULT_CHECKED_COLOR = Color.RED;
    private static final Color DEFAULT_UNCHECK_COLOR = Color.GRAY;
    private static final int DEFAULT_ANIM_DURATION = 150;
    private static final float VALUE_ZERO =0.0f;
    private static final float CHECK_DURATION =1.0f;
    private static int DEFAULT_RADIUS = 10;
    private Paint mCirclePaint;
    private Paint mLinePaint;

    private int radius;
    private int width, height;
    private int cx, cy;
    private float[] points = new float[6];
    private float correctProgress;
    private float downY;
    private boolean isChecked;
    private boolean toggle;
    private boolean isAnim;

    private int animDuration = DEFAULT_ANIM_DURATION;
    private Color unCheckColor = DEFAULT_UNCHECK_COLOR;
    private Color circleColor = DEFAULT_CHECKED_COLOR;
    private Color correctColor = DEFAULT_LINE_COLOR;
    private int correctWidth = DEFAULT_LINE_WIDTH;

    private OnCheckedChangeListener listener;

    public AnimateCheckBox(Context context) {
        this(context, null);
    }

    public AnimateCheckBox(Context context, AttrSet attrset) {
        super(context, attrset);

        circleColor = attrset.getAttr("checkedColor").isPresent() ?
                attrset.getAttr("checkedColor").get().getColorValue(): DEFAULT_CHECKED_COLOR;

        unCheckColor = attrset.getAttr("unCheckColor").isPresent() ?
                attrset.getAttr("unCheckColor").get().getColorValue() : DEFAULT_UNCHECK_COLOR;

        correctColor = attrset.getAttr("lineColor").isPresent() ?
                attrset.getAttr("lineColor").get().getColorValue() : DEFAULT_LINE_COLOR;

        correctWidth = attrset.getAttr("lineWidth").isPresent() ?
                attrset.getAttr("lineWidth").get().getDimensionValue() : DEFAULT_LINE_WIDTH;

        animDuration = attrset.getAttr("animDuration").isPresent() ?
                attrset.getAttr("animDuration").get().getIntegerValue() : DEFAULT_ANIM_DURATION;

        init(context);
        setLayoutRefreshedListener(this);
        addDrawTask(this::onDraw);
    }


    public static int dip2px(Context context, float dpValue) {
        Optional<Display> optionalDisplay = DisplayManager.getInstance().getDefaultDisplay(context);
        Display display = null;
        int density = 0;
        if (optionalDisplay.isPresent()) {
            display = optionalDisplay.get();
            if (display != null && display.getAttributes() != null) {
                final float scale = display.getAttributes().densityPixels;
                return (int) (dpValue * scale + 0.5f);
            }
        }

        return 0;
    }


    private void init(Context context) {

        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias( true);
        mCirclePaint.setStyle(Paint.Style.FILL_STYLE);
        mCirclePaint.setColor(circleColor);

        mLinePaint = new Paint();
        mLinePaint.setAntiAlias( true);
        mLinePaint.setStyle(Paint.Style.FILL_STYLE);
        mLinePaint.setColor(correctColor);
        mLinePaint.setStrokeWidth(correctWidth);

        setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component v) {
                if (isChecked) {
                    hideCorrect();
                } else {
                    showCheck();
                }
            }
        });
    }


    public boolean isChecked() {
        return isChecked;
    }


    public void setChecked(boolean checked) {
        if (isChecked && !checked) {
            hideCorrect();
        } else if (!isChecked && checked) {
            showCheck();
        }
        else{
            invalidate();
        }
    }

    public void setUncheckStatus() {
        isChecked = false;
        radius = DEFAULT_RADIUS;
        correctProgress = 0;
        invalidate();
    }


    @Override
    public void onRefreshed(Component component) {
        height = width = Math.min(getWidth() - getPaddingLeft() - getPaddingRight(), getHeight() - getPaddingBottom() - getPaddingTop());
        cx = getWidth() / 2;
        cy = getHeight() / 2;

        float r = height / 2f;
        points[0] = r / 2f + getPaddingLeft();
        points[1] = r + getPaddingTop();

        points[2] = r * 5f / 6f + getPaddingLeft();
        points[3] = r + r / 3f + getPaddingTop();

        points[4] = r * 1.5f + getPaddingLeft();
        points[5] = r - r / 3f + getPaddingTop();
        DEFAULT_RADIUS = radius = (int) (height * 0.125f);

        invalidate();
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {

        if (false ==isAnim && true == isChecked) {

            //This is case where just need to show final check without animation
            radius = (int) (CHECK_DURATION * height * 0.37f + height * 0.125f);
        }

        float f = (radius - height * 0.125f) / (height * 0.5f);
        mCirclePaint.setColor(new Color((int)evaluate(f, unCheckColor.getValue(), circleColor.getValue())));
        canvas.drawCircle(cx, cy, radius, mCirclePaint);

        if (correctProgress > 0) {
            if (correctProgress < 1 / 3f) {
                float x = points[0] + (points[2] - points[0]) * correctProgress;
                float y = points[1] + (points[3] - points[1]) * correctProgress;
                canvas.drawLine(points[0], points[1], x, y, mLinePaint);
            } else {
                float x = points[2] + (points[4] - points[2]) * correctProgress;
                float y = points[3] + (points[5] - points[3]) * correctProgress;
                canvas.drawLine(points[0], points[1], points[2], points[3], mLinePaint);
                canvas.drawLine(points[2], points[3], x, y, mLinePaint);
            }
        }
    }


    public void setCircleColor(Color color) {
        circleColor = color;
    }

    public void setLineColor(Color color) {
        mLinePaint.setColor(color);
    }

    public void setUnCheckColor(Color color) {
        unCheckColor = color;
    }

    private int evaluate(float fraction, int startValue, int endValue) {
        if (fraction <= 0) {
            return startValue;
        }
        if (fraction >= 1) {
            return endValue;
        }
        int startInt = startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return ((startA + (int) (fraction * (endA - startA))) << 24) | ((startR + (int) (fraction * (endR - startR))) << 16) | ((startG + (int) (fraction * (endG - startG))) << 8) | ((startB + (int) (fraction * (endB - startB))));
    }


    private void showUnChecked() {
        if (isAnim) {
            return;
        }

        isAnim = true;
        ValueAnimator va  = ValueAnimator.ofFloat(0f, 1.0f);
        va.setDuration(animDuration);
        va.setCurveType(Animator.CurveType.LINEAR);
        va.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {

                radius = (int) ((1 - value) * height * 0.375f + height * 0.125f);
                if (value >= 1) {
                    isChecked = false;
                    isAnim = false;
                    if (listener != null) {
                        listener.onCheckedChanged(AnimateCheckBox.this, false);
                    }
                }
                invalidate();
            }
        });

        va.start();
    }

    private void showCheck() {
            if (isAnim) {
                return;
            }
            isAnim = true;
            ValueAnimator va  = ValueAnimator.ofFloat(VALUE_ZERO, CHECK_DURATION);
            va.setDuration(animDuration);
            va.setCurveType(Animator.CurveType.LINEAR);
            va.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {

                    radius = (int) (value * height * 0.37f + height * 0.125f);
                    if (value >= 1) {
                        isChecked = true;
                        isAnim = false;
                        if (listener != null) {
                            listener.onCheckedChanged(AnimateCheckBox.this, true);
                        }
                        showCorrect();
                    }
                    invalidate();
                }
            });

           va.start();
    }


    private void showCorrect() {
            if (isAnim) {
                return;
            }
            isAnim = true;
            ValueAnimator va  = ValueAnimator.ofFloat(0.0f, 1.0f);
            va.setDuration(animDuration);
            va.setCurveType(Animator.CurveType.LINEAR);
            va.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {

                    correctProgress = value;
                    invalidate();
                    if (value >= 1) {
                        isAnim = false;
                    }
                }
            });

            va.start();
    }

    private void hideCorrect() {
            if (isAnim) {
                return;
            }
            isAnim = true;
            ValueAnimator va  = ValueAnimator.ofFloat(0.0f, 1.0f);
            va.setDuration(animDuration);
            va.setCurveType(Animator.CurveType.LINEAR);
            va.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {

                    correctProgress = 1 - value;
                    invalidate();
                    if (value >= 1) {
                        isAnim = false;
                        showUnChecked();
                    }
                }
            });

            va.start();
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        this.listener = listener;
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(Component buttonView, boolean isChecked);
    }
}

